package com.usiful.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.usiful.model.ActionArg;
import com.usiful.model.PluginInfo;
import com.usiful.model.RscXmlItem;
import com.usiful.utils.CommonUtil;
import com.usiful.utils.FileUtil;
import com.usiful.utils.PathUtil;

/**
 * 插件控制器
 * 
 * @author [周织能]
 *
 */
public class PluginController extends BaseController {

	public final static String ACTION_INSTALL = "add";
	public final static String ACTION_UNINSTALL = "remove";

	public final static String ARG_PROJECT_PATH = "project";
	public final static String ARG_OPERATE_TYPE = "operateType";
	public final static String ARG_PLUGIN_NAME = "name";
	public final static String ARG_PLUGIN_PATH = "file";
	public final static String ARG_PLUGIN_PKGNAME = "package";
	public final static String ARG_APP_PKGNAME = "apppackage";

	protected String mProjectPath;
	protected List<String> mPluginFiles;
	protected PluginInfo mPluginInfo;
	protected String mAppPkgName; // 用来更新插件文件中的R引用

	public PluginController() {
		mPluginFiles = new ArrayList<String>();
	}

	@Override
	public boolean handleAction(ActionArg arg) {
		// TODO Auto-generated method stub
		super.handleAction(arg);
		try {
			String action = arg.getArgValueMap().get(ARG_OPERATE_TYPE).get(0);
			mProjectPath = arg.getArgValueMap().get(ARG_PROJECT_PATH).get(0);
			if (action.equals(ACTION_INSTALL)) { // 安装插件
				if (!arg.getArgValueMap().containsKey(ARG_PLUGIN_PATH)) {
					throw new Exception("you hava lack the param: " + ARG_PLUGIN_PATH);
				}
				// 参数为插件的路径
				String pluginPath = arg.getArgValueMap().get(ARG_PLUGIN_PATH).get(0);
				if (!arg.getArgValueMap().containsKey(ARG_APP_PKGNAME)) {
					System.out.println("you hava lack the param: " + ARG_APP_PKGNAME);
					throw new Exception("you hava lack the param: " + ARG_APP_PKGNAME);
				}
				mAppPkgName = arg.getArgValueMap().get(ARG_APP_PKGNAME).get(0);
				installPlugin(pluginPath);
			} else if (action.equals(ACTION_UNINSTALL)) { // 卸载插件
				if (!arg.getArgValueMap().containsKey(ARG_PLUGIN_PKGNAME)) {
					throw new Exception("you hava lack the param: " + ARG_PLUGIN_PKGNAME);
				}
				if (!arg.getArgValueMap().containsKey(ARG_PLUGIN_NAME)) {
					throw new Exception("you hava lack the param: " + ARG_PLUGIN_NAME);
				}
				// 参数插件入口点
				String pluginEntry = arg.getArgValueMap().get(ARG_PLUGIN_PKGNAME).get(0) + "."
						+ arg.getArgValueMap().get(ARG_PLUGIN_NAME).get(0);
				uninstallPlugin(pluginEntry);

			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

	/**
	 * 检测是否被支持的xml文件
	 * 
	 * @param rscXml
	 * @return xml类型
	 */
	private String getVaildRscXmlType(String rscXml) {
		String type = null;
		if (rscXml.contains(RscXmlItem.RSC_COLORS + ".xml")) {
			type = RscXmlItem.RSC_COLORS;
		} else if (rscXml.contains(RscXmlItem.RSC_ARRAYS + ".xml")) {
			type = RscXmlItem.RSC_ARRAYS;
		} else if (rscXml.contains(RscXmlItem.RSC_COLORS + ".xml")) {
			type = RscXmlItem.RSC_COLORS;
		} else if (rscXml.contains(RscXmlItem.RSC_DIMENS + ".xml")) {
			type = RscXmlItem.RSC_DIMENS;
		} else if (rscXml.contains(RscXmlItem.RSC_STRINGS + ".xml")) {
			type = RscXmlItem.RSC_STRINGS;
		} else if (rscXml.contains(RscXmlItem.RSC_IDS + ".xml")) {
			type = RscXmlItem.RSC_IDS;
		} else if (rscXml.contains(RscXmlItem.RSC_STYLES + ".xml")) {
			type = RscXmlItem.RSC_STYLES;
		}
		return type;
	}

	/**
	 * 该接口只使用于文件类型符合RscXmlItem.{Type}的文件
	 * 
	 * @param rscXml
	 * @return
	 * @throws Exception
	 */
	private Map<String, Element> getRscItemMapByRscXml(String rscXml) throws Exception {
		String type = getVaildRscXmlType(rscXml);
		if (type == null) {
			throw new Exception("Unsuport xml file type!");
		}
		Map<String, Element> rscItemMap = new HashMap<String, Element>();
		SAXReader reader = new SAXReader();
		Document document;
		try {
			document = reader.read(new File(rscXml));
			Element rootElement = document.getRootElement();
			@SuppressWarnings("unchecked")
			Iterator<Element> it = rootElement.elementIterator();
			while (it.hasNext()) {
				Element e = it.next();
				if (e.attributeValue("name") == null) {
					continue;
				} else {
					rscItemMap.put(e.attributeValue("name"), e);
				}
			}
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return rscItemMap;
	}

	/**
	 * 合并两个相同类型的xml，合并结果===》targetRscXml
	 * 
	 * @param srcRscXml
	 * @param targetRscXml
	 */
	private boolean mergeRscXml(String srcRscXml, String targetRscXml) {
		boolean mergeResult = true;
		;
		String srcRscXmlType = getVaildRscXmlType(srcRscXml), targetRscXmlType = getVaildRscXmlType(targetRscXml);
		if (srcRscXmlType == null || targetRscXmlType == null || !srcRscXmlType.equals(targetRscXmlType)) {
			return false;
		}
		try {
			Map<String, Element> srcRscItemMap = getRscItemMapByRscXml(srcRscXml),
					targetRscItemMap = getRscItemMapByRscXml(targetRscXml),
					append2TargetRscMap = new HashMap<String, Element>();
			Set<String> srcRscKeySet = srcRscItemMap.keySet(), targetRscKeySet = targetRscItemMap.keySet();
			Element srcRscElement, targetRscElement;
			SAXReader reader = new SAXReader();
			Document document = reader.read(new File(targetRscXml));
			Element rootElement = document.getRootElement();

			for (String targetRscKey : targetRscKeySet) {
				targetRscElement = targetRscItemMap.get(targetRscKey);
				for (String srcRscKey : srcRscKeySet) {
					srcRscElement = srcRscItemMap.get(srcRscKey);
					if (targetRscElement.attributeValue("name").equals(srcRscElement.attributeValue("name"))) {
						// 存在同名称节点则忽略合并
					} else {
						append2TargetRscMap.put(srcRscElement.attributeValue("name"), srcRscElement);
					}
				}
			}
			if (append2TargetRscMap.size() > 0) {
				Set<String> keySet = append2TargetRscMap.keySet();
				List<String> appendPluginNodeList = new ArrayList<String>();
				for (String key : keySet) {
					rootElement.add((Element) append2TargetRscMap.get(key).clone());
					appendPluginNodeList.add(key); // 记录插件在/values/xxx.xml下的节点记录
				}
				mPluginInfo.getAppendValuesNodeMap().put(targetRscXmlType, appendPluginNodeList);
				System.out.println(document.asXML());
				OutputFormat format = OutputFormat.createPrettyPrint();
				format.setEncoding("UTF-8");
				XMLWriter writer = new XMLWriter(
						new OutputStreamWriter(new FileOutputStream(new File(targetRscXml)), "UTF-8"), format);
				writer.write(document);
				writer.flush();
				writer.close();
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			mergeResult = false;
			e.printStackTrace();
		}
		return mergeResult;
	}

	/**
	 * 扫描插件内容
	 * 
	 * @param plugin
	 */
	private void scanAndHandlePluginFiles(String plugin) {
		List<String> fileList = new ArrayList<String>();
		FileUtil.getFiles(plugin, fileList);
		String destFile = null;
		boolean isCopyOperation = true; // true 拷贝 false 合并
		for (String filePath : fileList) {
			isCopyOperation = true;
			// filePath = filePath.replace(file.getName(), "");
			if (filePath.contains("libs")) {
				destFile = PathUtil.getFullPath(mProjectPath, "/libs", PathUtil.getBackPath(filePath, "libs"));
			} else if (filePath.contains("assets")) {
				destFile = PathUtil.getFullPath(mProjectPath, "/assets", PathUtil.getBackPath(filePath, "assets"));
			} else if (filePath.contains("www")) {
				// js文件的目标位置
				destFile = PathUtil.getFullPath(mProjectPath, "/assets/www/plugins/",
						mPluginInfo.getPluginEntry().toLowerCase(), new File(filePath).getName());
				mPluginInfo.setWwwjs(new File(filePath).getName());
			} else if (filePath.contains("src")) {
				destFile = PathUtil.getFullPath(mProjectPath, "/src/",
						CommonUtil.extractJavaPackagePathOfJavaFile(filePath), new File(filePath).getName());
			} else if (filePath.contains("drawable")) {
				destFile = PathUtil.getFullPath(mProjectPath, "/res/drawable", new File(filePath).getName());
			} else if (filePath.contains("anim")) {
				destFile = PathUtil.getFullPath(mProjectPath, "/res/anim", new File(filePath).getName());
			} else if (filePath.contains("layout")) {
				destFile = PathUtil.getFullPath(mProjectPath, "/res/layout", new File(filePath).getName());
			} else if (filePath.contains("strings.xml") || filePath.contains("colors.xml")
					|| filePath.contains("styles.xml") || filePath.contains("dimens.xml")) {
				destFile = PathUtil.getFullPath(mProjectPath, "/res/values", new File(filePath).getName());
				isCopyOperation = false;
			}

			if (destFile != null) {
				if (isCopyOperation && destFile != null) { // 拷贝
					String movePath = new File(destFile).getParent();
					File movePathF = new File(movePath);
					if (!movePathF.exists()) {
						movePathF.mkdirs();
					}
					if (!new File(destFile).exists()) {
						FileUtil.copyFile(filePath, destFile, true);
						mPluginInfo.getPluginFiles().add(destFile); // 记录插件文件
					}
					if (destFile.contains(".java")) {
						CommonUtil.changeJavaR_Refrence(destFile, mAppPkgName);
					}
				} else { // 合并
					mergeRscXml(filePath, destFile);
				}
			}
		}
		String regFile = PathUtil.getFullPath(mProjectPath, "/pluginConf/",
				mPluginInfo.getPluginEntry().replace(".", "_") + ".json");
		FileUtil.writeString2File(new Gson().toJson(mPluginInfo), regFile);
	}

	/**
	 * 从插件记录文件中提取插件信息===>安装之后该方法才能用
	 * 
	 * @param pluginEntry
	 * @return
	 */
	private PluginInfo extractPluginInfoFromStateFile(String pluginEntry) {

		PluginInfo pluginInfo = new PluginInfo();
		try {
			String pluginConfFile = PathUtil.getFullPath(mProjectPath,
					"/pluginConf/" + pluginEntry.replace(".", "_") + ".json");
			GsonBuilder builder = new GsonBuilder();
			Gson gson = builder.create();
			System.out.println("pluginConfig=====>" + FileUtil.readFile2String(pluginConfFile));
			pluginInfo = gson.fromJson(FileUtil.readFile2String(pluginConfFile), PluginInfo.class);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return pluginInfo;
	}

	/**
	 * 从插件目录中的config.json提取插件信息
	 * 
	 * @param pluginPath
	 * @return
	 */
	private static PluginInfo extractPluginInfoFromConfigJson(String pluginPath) {
		PluginInfo pluginInfo = null;
		try {
			pluginInfo = new PluginInfo();
			String pluginConfStr = FileUtil.readFile2String(pluginPath);
			JSONObject pluginConfJson = new JSONObject(pluginConfStr);
			pluginInfo.setPackageName(pluginConfJson.getString("package"));
			pluginInfo.setIdentifier(pluginConfJson.getString("identifier"));
			pluginInfo.setPluginEntry(pluginConfJson.getString("pluginEntry"));
			if (pluginConfJson.has("clobbers")) {
				pluginInfo.setClobbers(pluginConfJson.getString("clobbers"));
			}
		} catch (UnsupportedEncodingException | JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return pluginInfo;
	}

	/**
	 * 处理项目中的cordova_plugins.js文件
	 * 
	 * @param pluginInfo
	 * @param addOrRemove
	 * @throws UnsupportedEncodingException
	 */
	private void handleCordovaPluginJs(PluginInfo pluginInfo, boolean addOrRemove) throws UnsupportedEncodingException {
		String cordovaPluginJs = FileUtil
				.readFile2String(PathUtil.getFullPath(mProjectPath, "/assets/www/cordova_plugins.js"));
		String startMagicStr = "module.exports = [", endMagicStr = "]";
		int startIdx, endIdx;
		startIdx = cordovaPluginJs.indexOf(startMagicStr) + startMagicStr.length();
		endIdx = cordovaPluginJs.lastIndexOf(endMagicStr);
		String prevStr = cordovaPluginJs.substring(0, startIdx),
				backStr = cordovaPluginJs.substring(endIdx, cordovaPluginJs.length()),
				operateStr = cordovaPluginJs.substring(startIdx, endIdx);
		if (addOrRemove) {
			String item = ",{\n\t";
			item += "\"file\":\"plugins/" + pluginInfo.getPluginEntry().toLowerCase() + "/www/" + pluginInfo.getWwwjs()
					+ "\",\n";
			item += "\t\"id\":\"" + pluginInfo.getPluginEntry() + "\",\n";
			if (pluginInfo.getClobbers() != null) {
				item += "\t\"clobbers\":[\n\t\"" + pluginInfo.getClobbers() + "\"\n\t]\n";
			}
			item += "}\n";
			operateStr += item;
			cordovaPluginJs = prevStr + operateStr + backStr;
			System.out.println(cordovaPluginJs);
		} else {
			String[] items = operateStr.split("}");
			operateStr = "";
			// pluginInfo.setPluginEntry("com.foreveross.chameleon.user.plugin.PingPlugin");
			String delMagicStr = "\"id\":\"" + pluginInfo.getPluginEntry() + "\"";
			System.out.println(delMagicStr);
			for (String item : items) {

				if (!item.contains(delMagicStr)) {
					operateStr += item + "}";
				}
			}
			System.out.println(operateStr);
			cordovaPluginJs = prevStr + operateStr + backStr;
			// System.out.println(cordovaPluginJs);
		}
		FileUtil.writeString2File(cordovaPluginJs,
				PathUtil.getFullPath(mProjectPath, "/assets/www/cordova_plugins.js"));
	}

	/**
	 * 处理 project/res/xml/config.xml文件中的插件信息
	 * 
	 * @param pluginInfo
	 */
	@SuppressWarnings("unchecked")
	private void handleConfigXml(PluginInfo pluginInfo) {
		String configXml = PathUtil.getFullPath(mProjectPath, "res/xml/config.xml");
		SAXReader reader = new SAXReader();
		Document document;
		try {
			document = reader.read(new File(configXml));
			Element rootElement = document.getRootElement();
			Iterator<Element> iterator = rootElement.elementIterator();
			boolean addPlugin = true, interrput = false;
			Element operatePluginNode = null;
			while (iterator.hasNext() && !interrput) {
				Element e = iterator.next();
				if (e.getName().equals("feature")) {
					Iterator<Element> it = e.elementIterator();
					do {
						Element paramNode = it.next();
						if (paramNode.getName().equals("param")
								&& paramNode.attributeValue("name").equals("android-package")) {
							if (paramNode.attributeValue("value").equals(pluginInfo.getPluginEntry())) {
								operatePluginNode = e;
								addPlugin = false;
								break;
							}
						}
					} while (it.hasNext());
				}
			}
			if (addPlugin) { // 增加插件声明
				Element pluginNode = rootElement.addElement("feature");
				String pluginEntry = pluginInfo.getPluginEntry();
				int idx = pluginEntry.lastIndexOf(".");
				String pluginAliasName = pluginEntry.substring(idx + 1, pluginEntry.length());
				pluginNode.addAttribute("name", pluginAliasName);
				Element pluginParamNode = pluginNode.addElement("param");
				pluginParamNode.addAttribute("name", "android-package");
				pluginParamNode.addAttribute("value", pluginInfo.getPluginEntry());
			} else { // 删除插件声明
				rootElement.remove(operatePluginNode);
			}
			System.out.println(document.asXML());
			OutputFormat format = OutputFormat.createPrettyPrint();
			format.setEncoding("UTF-8");
			XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream(new File(configXml)), "UTF-8"),
					format);
			writer.write(document);
			writer.flush();
			writer.close();
		} catch (DocumentException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * 删除插件在values/xxx.xml中的信息
	 * 
	 * @param type
	 * @param nodeStr
	 */
	private void removePluginInfoInValuesXml(String type, List<String> nodeList) {
		String xmlFile = PathUtil.getFullPath(mProjectPath, "/res/values/", type + ".xml");
		SAXReader reader = new SAXReader();
		Document document;
		try {
			Map<String, String> nodesMap = new HashMap<String, String>();
			for (String node : nodeList) {
				nodesMap.put(node, node);
			}
			document = reader.read(new File(xmlFile));
			Element rootElement = document.getRootElement();
			@SuppressWarnings("unchecked")
			Iterator<Element> iterator = rootElement.elementIterator();
			String name;
			while (iterator.hasNext()) {
				Element e = iterator.next();
				name = e.attributeValue("name");
				if (nodesMap.containsKey(name)) {
					rootElement.remove(e); // 删除节点
				}
			}
			OutputFormat format = OutputFormat.createPrettyPrint();
			format.setEncoding("UTF-8");
			XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream(new File(xmlFile)), "UTF-8"),
					format);
			writer.write(document);
			writer.flush();
			writer.close();
		} catch (DocumentException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * 清除插件的信息
	 * 
	 * @param pluginInfo
	 */
	private void clearPluginFiles(String pluginEntry) {
		String confFile = PathUtil.getFullPath(mProjectPath, "/pluginConf/", pluginEntry.replace(".", "_") + ".json");
		try {
			String pluginInfoStr = FileUtil.readFile2String(confFile);
			PluginInfo pluginInfo = new Gson().fromJson(pluginInfoStr, PluginInfo.class);
			Map<String, List<String>> clearValuesNodeMap = pluginInfo.getAppendValuesNodeMap();
			Set<String> clearKeySet = clearValuesNodeMap.keySet();
			for (String key : clearKeySet) {
				removePluginInfoInValuesXml(key, clearValuesNodeMap.get(key));
			}
			List<String> clearFileList = pluginInfo.getPluginFiles();
			File clearFile;
			for (String clearFilePath : clearFileList) {
				clearFile = new File(clearFilePath);
				if (clearFile.exists()) {
					clearFile.delete();
				}
			}
			new File(confFile).delete();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 安装插件
	 */
	private void installPlugin(String pluginPath) {
		mPluginInfo = extractPluginInfoFromConfigJson(PathUtil.getFullPath(pluginPath, "/config.json"));
		try {
			scanAndHandlePluginFiles(pluginPath);
			handleCordovaPluginJs(mPluginInfo, true);
			handleConfigXml(mPluginInfo);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 卸载插件
	 */
	private void uninstallPlugin(String pluginEntry) {
		mPluginInfo = extractPluginInfoFromStateFile(pluginEntry);
		try {
			handleCordovaPluginJs(mPluginInfo, false);
			handleConfigXml(mPluginInfo);
			clearPluginFiles(pluginEntry);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
